#ifndef _hcnode
#define _hcnode

#include <iostream>
#include <map>
#include <pthread.h>
#include <stdint.h>
#include <math.h>
#include <string>

#define NOTIFY 0
#define GETPRED 1
#define FINDSUCCESS 2
#define GETSUCCESS 3
#define CLOSESTPREC 4
#define ADDFILE 5
#define DELFILE 6
#define GETFILE 7
#define GETTABLE 8


using namespace std;


class cmyID
{
	public:
		int id;
		int port;
		cmyID()
		{
			id = -1;
			port = -1;
		}
		cmyID(int _id,int _port)
		{
				id=_id;
				port=_port;
		}
		cmyID(const cmyID & other)
		{
				id = other.id;
				port = other.port;
		}
		void setID(int _id, int _port)
		{
			id = _id;
			port = _port;
		}

		bool checkRange(cmyID & endid, cmyID &check, int m)
		{	
			if(!(isNULL() || endid.isNULL() || check.isNULL()))
			{
				cmyID new_end(endid), new_check(check);
				if(endid.id < id)
				{
					new_end.id += pow(2, m);
					if(id > new_check.id)
					new_check.id += pow(2,m);
				}
				if((new_check.id>id && new_check.id<new_end.id) ||
					(new_check.id<id && new_check.id>new_end.id))
						return true;
			}
			return false;
		}
		bool Compare (cmyID& d)
		{
				return (id==d.id) && (port==d.port) && (id==d.id);
		}
		bool operator== (cmyID& d)
		{
				return Compare(d);
		}
		bool checkRangeEq(cmyID & endid, cmyID &check, int m)
		{
			
			if(!(isNULL() || endid.isNULL() || check.isNULL()))
			{
					
				cmyID new_end(endid), new_check(check);
				
				if(endid.id < id)
				{
					 
					new_end.id += pow(2, m);
					if(id > new_check.id)
					{
						new_check.id += pow(2,m);
					}
				}
				if((new_check.id>id && new_check.id <= new_end.id) ||
					(new_check.id>=new_end.id && new_check.id < id))
						return true;
			}
			
			return false;
		}
		bool isNULL()
		{
				return (id==-1) && (port==-1);
		}
		

};
class finger
{
	public:
	
		int start;
		cmyID node;
		cmyID successor;
		cmyID predecessor;
		finger()
		{
			start = 0;
		}
		finger(int m, int k,int n)
		{
			start =(int) (n + pow(2, k-1)) % (int)pow(2,m); 
		}
};
class file{
	public:
		string fname;
		string data;
		file()
		{
			fname = "";
			data = "";
		}
		file(int _fname, int _data)
		{
			fname = _fname;
			data = _data;
		}
};

class myNode{
	public:
		int m;	
		map<int, finger> finger_table;
		map<int, file> files;
		cmyID introducer;
		cmyID successor;
		cmyID predecessor;
		cmyID mine;
		int stabilizeInterval, fixInterval;
		pthread_rwlock_t lock;
	cmyID local_find_successor(cmyID reqID);
	cmyID local_find_predecessor(cmyID reqID);
	cmyID local_closest_preceding_finger(cmyID reqID);
	void local_notify(cmyID node);
	void join(cmyID introducer);
	string local_add_file(int key, string data, string fname);
	string local_del_file(int key,string fname);
	void transfer_files(cmyID node);
	string local_get_file(int key, string fname);
	string local_get_table(int id);
	myNode(int _m,int _id, int _port, cmyID _introducer)
	{
		m=_m;
		introducer = _introducer;
		mine.setID(_id,_port);
		predecessor.setID(-1,-1);
		pthread_rwlock_init(&lock, NULL);
		//join(introducer);
		for(int i = 1; i <= m; i++)
		{
			finger_table[i].start = (mine.id+ (int) pow(2, i-1)) % (int)pow(2,m);
			finger_table[i].node.setID(-1,-1);
		}
	}
		// Wrapper around the remote procedure call
		// 		port: port to send message
		// 		arg: arguments to call
		// 		choice: function call
		cmyID rpCall(uint32_t port, cmyID arg, int choice);
		string rpCallString(uint32_t port, int key_id, string data, string fname, int choice);
		cmyID findFile(int key);
};

struct arg_struct
{
	int port;
	myNode * node;
};
#endif
